package system

import bus._
import chisel3._
import chisel3.util._
import common.Constants._

class FakeL2Cache extends Module {
  val io = IO(new Bundle() {
    val in = Vec(2, Flipped(new CoreLinkIO(nWords*XLEN)))
    val out = new CoreLinkIO(XLEN)
  })

  val port = RegInit(0.U(log2Up(2).W))

  val thisReq = Wire(Decoupled(new CoreReqIO(XLEN)))
  val chosen = Wire(UInt(2.W))
  for (i <- 0 until 2) {
    when(io.in(i).req.valid){
      chosen := i.U
      thisReq := io.in(i).req
    }
  }

  /**
   * brust
   */
  val idle :: memAddrReq :: memWriteReq :: memReadResp :: memWriteResp :: release :: Nil = Enum(6)
  val reg_state = RegInit(idle)

  val dataReadBuffer = RegInit(0.U((nWords*XLEN).W))

  val readBeatCnt = Counter(nWords)
  val writeBeatCnt = Counter(nWords)
  val len = (nWords - 1).U

  val addr = Cat(thisReq.bits.addr(63, 3), 0.U(3.W))
  val rWord = io.out.resp.bits.rdata

  val startAddr = RegEnable(next = addr, init = 0.U(XLEN.W), enable = thisReq.fire())
  val size = RegEnable(next = thisReq.bits.size, init = 0.U(3.W), enable = thisReq.fire())
  val axi_wen = RegEnable(next = thisReq.bits.cmd === LinkBusCmd.write, init = false.B, enable = thisReq.fire())
  val dataWriterBuffer = RegEnable(next = thisReq.bits.wdata, init = 0.U(XLEN.W), enable = thisReq.fire())

  val wlast = Mux(writeBeatCnt.value === (nWords - 1).U, true.B, false.B)
  val rlast = io.out.resp.bits.isReadLast()
  val wblock = WireInit(dataWriterBuffer.asTypeOf(Vec(nWords, UInt(XLEN.W))))

  /**
   * L2 Cache State transform
   */
  switch(reg_state){
    is(idle){
      when(thisReq.fire()) {
        port := chosen
        reg_state := memAddrReq
        when(thisReq.bits.isRead()) {
          reg_state := memReadResp
        }.elsewhen (thisReq.bits.isWrite()) {
          reg_state := memWriteResp
        }
      }
    }
    is(memAddrReq) {
      when(io.out.req.fire()) {
        reg_state := memReadResp
        when(thisReq.bits.isRead()) {
          reg_state := memReadResp
        }.elsewhen (thisReq.bits.isWrite()) {
          reg_state := memWriteResp
        }
        readBeatCnt.value := (startAddr >> size) & len
      }
    }
    is(memWriteReq) {
      when(io.out.req.fire()){
        writeBeatCnt.inc()
        when(wlast){
          writeBeatCnt.value := 0.U
          reg_state := memWriteResp
        }
      }
    }
    is(memReadResp) {
      when(io.out.resp.fire()){
        val nextBlock = dataReadBuffer.asTypeOf(Vec(nWords, UInt(XLEN.W)))
        nextBlock(readBeatCnt.value) := rWord
        dataReadBuffer := nextBlock.asUInt()
        readBeatCnt.inc() // a step
        when(rlast){
          readBeatCnt.value := 0.U
          reg_state := release
        }
        when (readBeatCnt.value === len) {
          readBeatCnt.value := 0.U
        }
      }
    }
    is(memWriteResp) {
      when(io.out.resp.fire()) {
        reg_state := release
      }
    }
    is(release) {
      when(io.in(port).resp.fire()){
        reg_state := idle
      }
    }
  }

  io.in.map(_.resp.bits := io.out.resp.bits)
  io.in.map(_.resp.valid := false.B)

  io.in(port).resp.valid := io.out.resp.valid

  thisReq.ready := io.out.req.ready && (reg_state === idle)

  io.out.req.valid := reg_state === memAddrReq
  io.out.req.bits.wdata := wblock(writeBeatCnt.value)
  io.out.resp.ready := io.in(port).resp.ready
}
